// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework.dbpool;

import java.sql.SQLException;
import java.sql.SQLWarning;

import com.eva.epc.common.util.CommonUtils;
import com.eva.epc.common.util.EException;

public class DBUtils
{
	/**
	 * 据各参数组织成一个Select语句.
	 * 
	 * @param table 要查询的数据库表名（可以是诸如a join b on a.xx=b.xx等组合）
	 * 		，可无表名(如调用函数时就不需要)
	 * @param columns 要查询的列（多列之间用逗号分隔，形如：f1,f2,f3）
	 * @param where where条件（可无）
	 * @return
	 */
	public static String formatGetComboItemsSql(String table, String columns, String where)
	{
		return formatGetComboItemsSql(table, columns, where, null);
	}
	/**
	 * 据各参数组织成一个Select语句.
	 * 
	 * @param table 要查询的数据库表名（可以是诸如a join b on a.xx=b.xx等组合）
	 * 		，可无表名(如调用函数时就不需要)
	 * @param columns 要查询的列（多列之间用逗号分隔，形如：f1,f2,f3）
	 * @param where where条件（可无）
	 * @param other 额外的SQL，如可以是order by someColumn或group by someColumn等
	 * @return
	 */
	public static String formatGetComboItemsSql(String table, String columns
			, String where, String other)
	{
		StringBuffer sql = new StringBuffer("SELECT ")
			.append(columns);
		//可无表名(如调用函数时就不需要)
		if(!CommonUtils.isStringEmpty(table))
			sql.append(" FROM ").append(table);
//		if(where != null && where.length() > 0)
		if(!CommonUtils.isStringEmpty(where,true))
			sql.append(" WHERE ").append(where).append("  ");
		if(!CommonUtils.isStringEmpty(other,true))
			sql.append(other);
		return sql.toString();
	}

	/**
	 * <p>
	 * 解析SQLException并组织成EException实例返回.<br>
	 * <b>注：</b>在解析每条异常的message时，可能要去掉标识，标识的长度由 {@link DBShell#dbExceptionIdentStringLen}指明.<br>
	 * 数据库异常头标记长度，一般都是固定长度，这些标记在此处被认为是多余的，每条都有，形如：
	 * “ORA-00001”中的"ORA-"或[Miscrosoft SQL Server ...]......"的”[Miscrosoft SQL Server ...]“这一
	 * 段一长度，在读取异常message时，就会把这个长度之前截断，因为它对最晒图用户而言不友好.
	 * </p>
	 * 
	 * @param ex 原始SQLException对象，可能包含多条异常信息，见方法 {@link SQLException#getNextException()}
	 * @return 封装了一个短异常描述消息和详细描述消息的EException对象(是否加入详细描述由参数needDetail指明)
	 * @see EException
	 * @see DBShell#needParseNextExceptionsForSQLException
	 * @see DBShell#dbExceptionIdentStringLen
	 */
	public static EException getErrorMessage(SQLException ex
//			,boolean needDetail
			)
	{		
		String shortMessage = CommonUtils.isStringEmpty(ex.getLocalizedMessage())
			?ex.getMessage():ex.getLocalizedMessage();
		String detailMessage = null;
		int i = DBShell.dbExceptionIdentStringLen;
		if (i > 0)
			shortMessage = shortMessage.substring(i);
		if (DBShell.needParseNextExceptionsForSQLException)
		{
			for (SQLException next = ex.getNextException();
				next != null && !(next instanceof SQLWarning);
				next = next.getNextException() )
			{
				String nextMsg = next.getLocalizedMessage();
				if (i > 0)
					nextMsg = nextMsg.substring(i);
				detailMessage = detailMessage.concat(String.valueOf("\r\n")).concat(nextMsg);
			}
		}

		return new EException(
				shortMessage == null ? ex.toString() : shortMessage
				,detailMessage == null ? ex.toString() : detailMessage
				,ex == null? null: ex.getCause());
	}

	/**
	 * 创建以<code>,</code>分隔<code>size</code>个<code>?</code>的字符串。
	 * 用于SQL预提交语句参数值的占位符部分。
	 *
	 * @param size <code>?</code>个数。
	 * @return 以<code>?</code>开始，后续<code>,</code>和<code>?</code>交替出现的
	 *       字符串缓冲。
	 * @since 2.1
	 */
	public static char[] createQuestionMarks(int size)
	{
		if(size < 1)
			throw new IllegalArgumentException("size < 1");
		char[] bf = new char[(size << 1) - 1];
		for (int i = 0; i < size; i++)
		{
			bf[(i << 1)] = '?';
			if(i < size - 1)
				bf[(i << 1) + 1] = ',';
		}
		return bf;
	}

	/**
	 * 已知SQL字段列表，计算字段个数
	 * 各个字段是以逗号（,）分隔的。单引号、双引号、圆括号（()）之内的逗号不是用来分隔字段的
	 * @param fieldList 由逗号分隔的字段列表字符串
	 * @return 字段个数。如果fieldList为空字符串，则返回0，否则为逗号个数 + 1
	 */
	public static int countField(String fieldList)
	{
		char[] chrs = fieldList.toCharArray();
		if (chrs.length == 0)
			return 0;
		int cnt = 0;//字段个数
		boolean notInQuatation = true;//不在单引号内
		boolean notInDblQuatation = true;//不在双引号内
		int parenthesis = 0;//在括号内
		int chr;
		for (int i = 1; i < chrs.length; i++)
		{
			chr = chrs[i];
			if (chr == '\'' && notInDblQuatation)
				//不在双引号内的单引号
				notInQuatation = !notInQuatation;
			else if(chr == '"' && notInQuatation)
				//不在单引号内的双引号
				notInDblQuatation = !notInDblQuatation;
			else if (notInQuatation && notInDblQuatation)
			{
				if (chr == ',' && parenthesis == 0)
					//不在引号和括号内的逗号才断定是分隔字段的
					cnt ++;
				else if (chr == '(')
					parenthesis ++ ;
				else if(chr == ')')
					parenthesis --;
			}
		}
		return cnt + 1;
	}

	/**
	 * 给定字段值，构建该字段值上的查询条件
	 * @param value 字段值
	 * @return (value == null || value.length() == 0)?
	 *				  " IS NULL":"='".concat(value).concat("'");
	 */
	public static String createIfNullFilter(String value)
	{
		return (value == null || value.length() == 0)?
				" IS NULL":"='".concat(value).concat(String.valueOf('\''));
	}

	/**
	 * 构造指定字段列表上的插入语句，字段的值都是?，插入语句的执行由
	 * {@link #prepareUpdate(String, Object[], boolean)}完成。
	 * @param table 实体表名，或带有INSTEAD OF 触发器的视图名
	 * @param fieldList 符合SQL语法的字段列表字符串
	 * @return 完整的插入语句，如：INSERT INTO table(fieldList)VALUES(?,?,...)
	 *
	 * @see #createQuestionMarks
	 * @see #countField
	 */
	public static String prepareInsertStmt(String table, String fieldList)
	{
		return new StringBuffer("INSERT INTO ").append(table)
		.append('(')
		.append(fieldList)
		.append(")VALUES(")
		.append(createQuestionMarks(countField(fieldList)))
		.append(')')
		.toString();
	}

	/**
	 * 构造指定字段列表上的插入语句，字段的值都是?，插入语句的执行由
	 * {@link #prepareUpdate(String, Object[], boolean)}完成。
	 * @param table 实体表名，或带有INSTEAD OF 触发器的视图名
	 * @param fields 字段名字符串数组。数组长度应该大于0。
	 * @return 完整的插入语句，如：INSERT INTO table(filedList)VALUES(?,?,...)
	 *
	 * @see #createQuestionMarks
	 */
	public static String prepareInsertStmt(String table, String[] fields)
	{
		StringBuffer sql = new StringBuffer("INSERT INTO ").append(table)
		.append('(');
		int cntField = fields.length;
		for (int i = 0; i < cntField; i++)
		{
			if ( i > 0)
				sql.append(',');
			sql.append(fields[i]);
		}
		return sql.append(")VALUES(")
			.append(createQuestionMarks(cntField))
			.append(')')
			.toString();
	}
	//	/** The charaters of String <code>)VALUES(</code>. */
	//	static char[] values = new char[]{')', 'V', 'A', 'L', 'U', 'E', 'S', '('};

	/**
	 * 构造指定字段列表上的修改语句，修改字段的值和筛选条件中字段的值都是?。构造好的语句的执行由
	 * {@link #prepareUpdate(String, Object[], boolean)}完成。
	 *
	 * @param table 实体表名，或带有INSTEAD OF 触发器的视图名
	 * @param updatedFields 待修改的字段列表。如果待修改的字段只有一个，则该参数为字段名字符串；
	 *                   如果待修改的字段的个数大于1，则该参数为这些字段的字符串数组。
	 * @param primaryKeyFields 确定修改记录的关键字段，如果为单个字段，则为该字段字符串，
	 *             如果为多个字段，则为各字段字符串组成的字符串数组。如果不需要确定修改的记录，
	 *             即修改的是整张实体表中的记录，则置该参数值为null。
	 * @return 完整的修改语句。如：UPDATE table SET f1=?,f2=?,f3=? WHERE p1=? AND p2=?
	 */
	public static String prepareUpdateStmt(String table
			, Object updatedFields
			, Object primaryKeyFields)
	{
		StringBuffer sql = new StringBuffer("UPDATE ").append(table)
		.append(" SET ");
		//添加字段修改部分
		appendEqual(sql, updatedFields, false);
		//构造where语句部分
		if (primaryKeyFields != null)
		{
			sql.append(" WHERE ");
			appendEqual(sql, primaryKeyFields, true);
		}

		return sql.toString();
	}

	/**
	 * 构造指定字段列表上的删除语句，筛选条件中字段的值都是?。构造好的语句的执行由
	 * {@link #prepareUpdate(String, Object[], boolean)}完成。
	 *
	 * @param table 实体表名，或带有INSTEAD OF 触发器的视图名
	 * @param primaryKeyFields 确定修改记录的关键字段，如果为单个字段，则为该字段字符串，
	 *             如果为多个字段，则为各字段字符串组成的字符串数组。如果不需要确定修改的记录，
	 *             即修改的是整张实体表中的记录，则置该参数值为null。
	 * @return 完整的删除语句。如：DELETE FROM table WHERE p1=? AND p2=?
	 */
	public static String prepareDeleteStmt(String table, Object primaryKeyFields)
	{
		StringBuffer sql = new StringBuffer("DELETE FROM ").append(table);
		//构造where语句部分
		if (primaryKeyFields != null)
		{
			sql.append(" WHERE ");
			appendEqual(sql, primaryKeyFields, true);
		}

		return sql.toString();
	}

	/**
	 * 将指定的一个或多个字段以 <code>字段[i]=?delim字段[i-1]=?</code> 的形式附加指定的SQL语句后面
	 * @param sql 存储SQL语句的字符串缓冲
	 * @param fields 单个字段字符串后者多个字段组成的字符串数组
	 * @param delimIsAnd 分隔各字段部分是否为字符串<code>" AND "</code>，如果不是，则就是字符<code>,</code>。
	 */
	public static void appendEqual(StringBuffer sql, Object fields, boolean delimIsAnd)
	{
		String[] fieldArray;
		if (fields instanceof String[])
		{
			fieldArray = (String[])fields;
			for (int i = 0; i < fieldArray.length; i++)
			{
				if (i > 0)
				{
					if (delimIsAnd)
						sql.append(" AND ");
					else
						sql.append(',');
				}
				sql.append(fieldArray[i]).append("=?");
			}
		}
		else
			sql.append(fields).append("=?");
	}
}
